/*
 * License GNU LGPL
 * Copyright (C) 2013 Amrullah <amrullah@panemu.com>.
 */
package com.abc.cheque.ui.table;

import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.util.Callback;

/**
 *
 * @author Amrullah <amrullah@panemu.com>
 */
public class TickColumn<T> extends TableColumn<T, Boolean> {

//    private Map<T, Boolean> mapValue = new HashMap<>();
//    private ObservableList<T> tickedRecords = FXCollections.observableArrayList();
    private ReadOnlyListWrapper<T> tickedRecords = new ReadOnlyListWrapper<T>(FXCollections.<T>observableArrayList());
    private ReadOnlyListWrapper<T> untickedRecords = new ReadOnlyListWrapper<>(FXCollections.<T>observableArrayList());
    private CheckBox chkHeader = new CheckBox();

    public TickColumn() {
        super();
        setSortable(false);
        setGraphic(chkHeader);
        chkHeader.setSelected(defaultTicked.get());
        defaultTicked.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                chkHeader.setSelected(newValue);
            }
        });
        setText(null);
        setCellFactory(new Callback<TableColumn<T, Boolean>, TableCell<T, Boolean>>() {
            @Override
            public TableCell<T, Boolean> call(TableColumn<T, Boolean> param) {
                return new TickCell();
            }
        });
        setCellValueFactory(new Callback<TableColumn.CellDataFeatures<T, Boolean>, ObservableValue<Boolean>>() {
            @Override
            public ObservableValue<Boolean> call(CellDataFeatures<T, Boolean> param) {
                boolean val = defaultTicked.get();
                if (tickedRecords.contains(param.getValue())) {
                    val = true;
                } else if (untickedRecords.contains(param.getValue())) {
                    val = false;
                }
                return new SimpleBooleanProperty(val);
            }
        });

        tableViewProperty().addListener(new ChangeListener<TableView<T>>() {
            @Override
            public void changed(ObservableValue<? extends TableView<T>> observable, TableView<T> oldValue, TableView<T> newValue) {
                if (newValue != null) {
                    /**
                     * The content of tickedRecords + untickedRecords should
                     * always equal with TableView's items
                     */
                    getTableView().getItems().addListener(new ListChangeListener<T>() {
                        @Override
                        public void onChanged(Change<? extends T> change) {
                            while (change.next()) {
                                if (change.wasRemoved()) {
                                    untickedRecords.removeAll(change.getRemoved());
                                    tickedRecords.removeAll(change.getRemoved());
                                } else if (change.wasAdded()) {
                                    if (defaultTicked.get()) {
                                        tickedRecords.get().addAll(change.getAddedSubList());
                                    } else {
                                        untickedRecords.get().addAll(change.getAddedSubList());
                                    }
                                }
                            }
                        }
                    });
                }
            }
        });

        chkHeader.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                if (chkHeader.isSelected()) {
                    untickedRecords.clear();
                    tickedRecords.setAll(getTableView().getItems());
                } else {
                    tickedRecords.clear();
                    untickedRecords.setAll(getTableView().getItems());
                }
                /**
                 * By toggling the visible attribute, the cell will be updated
                 */
                TickColumn.this.setVisible(false);
                TickColumn.this.setVisible(true);
            }
        });
    }
    private BooleanProperty defaultTicked = new SimpleBooleanProperty(false);

    public boolean isDefaultTicked() {
        return defaultTicked.get();
    }

    /**
     * Sets whether the row is by default ticked or not
     * @param ticked 
     */
    public void setDefaultTicked(boolean ticked) {
        defaultTicked.set(ticked);
    }

    /**
     * Gets property of defaultTicked
     * @return 
     */
    public BooleanProperty defaultTickedProperty() {
        return defaultTicked;
    }

    private void setHeaderSelected(boolean selected) {
        chkHeader.setSelected(selected);
    }

    /**
     * Check if passed item is ticked
     *
     * @param item
     * @return
     */
    public Boolean isTicked(T item) {
        if (tickedRecords.contains(item)) {
            return true;
        }
        return false;
    }

    /**
     * Set passed item to be ticked or unticked
     *
     * @param item
     * @param value
     */
    public void setTicked(T item, boolean value) {
        if (value) {
            untickedRecords.remove(item);
            if (!tickedRecords.contains(item)) {
                tickedRecords.add(item);
            }
        } else {
            tickedRecords.remove(item);
            if (!untickedRecords.contains(item)) {
                untickedRecords.add(item);
            }
        }
//        System.out.println("ticked: " + tickedRecords.size() + " :: unticked: " + untickedRecords.size());
    }

    /**
     * Gets tickedRecords property. This property is synchronized with
     * {@link #untickedRecordsProperty()}.
     *
     * @return
     */
    public ReadOnlyListProperty<T> tickedRecordsProperty() {
        return tickedRecords.getReadOnlyProperty();
    }

    /**
     * Gets untickedRecords property. This property is synchronized with
     * {@link #tickedRecordsProperty()}.
     *
     * @return
     */
    public ReadOnlyListProperty<T> untickedRecordsProperty() {
        return untickedRecords.getReadOnlyProperty();
    }

    public List<T> getTickedRecords() {
        return tickedRecords.get();
    }

    public List<T> getUntickedRecords() {
        return untickedRecords.get();
    }

    private class TickCell extends TableCell<T, Boolean> {

        private CheckBox progressBar = new CheckBox();

        public TickCell() {
            super();
            setGraphic(progressBar);
            progressBar.setAlignment(Pos.CENTER);
            setText(null);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            progressBar.setMaxWidth(Double.MAX_VALUE);
            contentDisplayProperty().addListener(new ChangeListener<ContentDisplay>() {
                private boolean suspendEvent = false;

                @Override
                public void changed(ObservableValue<? extends ContentDisplay> observable, ContentDisplay oldValue, ContentDisplay newValue) {
                    if (suspendEvent) {
                        return;
                    }
                    if (newValue != ContentDisplay.GRAPHIC_ONLY) {
                        suspendEvent = true;
                        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                        suspendEvent = false;
                    }
                }
            });

            progressBar.selectedProperty().addListener(new ChangeListener<Boolean>() {
                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    setTicked((T) getTableRow().getItem(), newValue);
                    if (!newValue) {
                        setHeaderSelected(false);
                    } else {
                        setHeaderSelected(untickedRecords.isEmpty());
                    }
                }
            });
        }

        @Override
        protected void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);
            if (!empty) {
                setGraphic(progressBar);
                if (getTableRow() != null) {
                    progressBar.setSelected(isTicked((T) getTableRow().getItem()));
                }
            } else {
                setGraphic(null);
            }
        }
    }
}
